<?php
/*
 * MiniMe - a minimalistic OStatus complient microblogging tool
 * Copyright (C) 2010, Tuomas Koski - koski.tuomas@gmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @author     Tuomas Koski <koski.tuomas@gmail.com>
 * @copyright  Tuomas Koski
 * @license    http://www.gnu.org/licenses/lgpl.txt
 */
class Discovery_Uri {
    
    public $__debugoutput = false;
    
    const NOT_DEFINED_URI = "";
    
    private $atomFeedUri = null;
    private $uri;
    private $domain;
    private $hostMetaProtocol = "http";
    private $webfingerProfilePage;
    
    /**
     * @var Atom_Feed
     */
    private $atomFeed = null;
    
    /**
     * @var Xrd_HostMeta
     */
    private $hostmeta = null;
    
    /**
     * @var Xrd_Lrdd
     */
    private $lrdd;
    
    public function __construct($uri) {
        
        $uri = urldecode($uri);
        
        if(substr($uri, 0, 5) == 'http:') {
            $domain = parse_url($uri, PHP_URL_HOST);
            $this->setDomain($domain);
        } else if(substr($uri, 0, 6) == 'https:') {
            $domain = parse_url($uri, PHP_URL_HOST);
            $this->setDomain($domain);
            $this->setHostMetaProtocol('https');
        } else if(substr($uri, 0, 5) == 'acct:') {
            $pieces = explode('@', $uri);
            if(isset($pieces[1])) {
                $this->setDomain($pieces[1]);
            } else {
                $this->setDomain(self::NOT_DEFINED_URI);
            }
        } else {
            $pieces = explode('@', $uri);
            if(isset($pieces[1])) {
                $this->setDomain($pieces[1]);
                $uri = "acct:" . $uri;
            } else {
                $this->setDomain(self::NOT_DEFINED_URI);
            }
            
        }
        $this->setUri($uri);
    }
    
    public function discover() {

        if(!$this->discoverHostMetaAndLrdd()) {
            //We could not find host meta and lrdd or the 
            //discovery failed with the given uri.
            
            //Maybe the Uri we have is the webfinger-profile page.
            $this->discoverWebfingerProfilePage();
        }
        
        if(empty($this->atomFeedUri)) {
            // We cannot continue - at least atom feed is needed to be able
            // to subscribe to something.
            return false;
        }
        
        return $this->discoverAtomFeed();
    }
    
    public function getAtomFeedUri() {
        return $this->atomFeedUri;
    }
    
    /**
     * @return Atom_feed
     */
    public function getAtomFeed() {
        return $this->atomFeed;
    }
    
    public function getHubUri() {
        if( $this->atomFeed != null && $this->atomFeed->getHub() != null ) {
            return $this->atomFeed->getHub()->getHref();
        }
        return self::NOT_DEFINED_URI;
    }
    
    public function getSalmonUri() {
        
        if( $this->atomFeed != null && $this->atomFeed->getSalmon() != null ) {
            return $this->atomFeed->getSalmon()->getHref();
        }
        
        if( $this->lrdd != null && $this->lrdd->getLinkSalmon() != null ) {
            return $this->lrdd->getLinkSalmon()->getHref();
        }
        
        return self::NOT_DEFINED_URI;
    }
    
    public function getMagicPublicKey() {
        
        if($this->lrdd != null && $this->lrdd->getLinkMagicPublicKey() != null ) {
            return $this->lrdd->getLinkMagicPublicKey()->getHref();
        }
        
        return self::NOT_DEFINED_URI;
    }
    
    public function getWebfingerProfilePageUri() {

        if($this->webfingerProfilePage) {
            return $this->webfingerProfilePage;
        }
        
        if( $this->lrdd->getLinkWebfingerProfilePage() != null ) {
            return $this->lrdd->getLinkWebfingerProfilePage()->getHref();
        }
        
        if($this->atomFeed->getAlternateHtml() != null ) {
            return $this->atomFeed->getAlternateHtml()->getHref();
        }
        
        return self::NOT_DEFINED_URI;
    }
    
    private function getFeedsLogo() {
        if($this->atomFeed->getLogo()) {
            return $this->atomFeed->getLogo()->getLogo();
        }
        return self::NOT_DEFINED_URI;
    }
    
    private function getActivitySteamsSubjectAvatar() {
        if($this->atomFeed->getActivityStreamSubject() && 
           $this->atomFeed->getActivityStreamSubject()->getAvatar()) {
            return $this->atomFeed->getActivityStreamSubject()->getAvatar()->getHref();
        }
        return self::NOT_DEFINED_URI;
    }
    
    public function getLogo() {
        $avatar = $this->getActivitySteamsSubjectAvatar();
        if(!empty($avatar)) {
            return $avatar;
        }
        return $this->getFeedsLogo();
    }
    
    private function getFeedsAuthorName() {
        if($this->atomFeed->getAuthor() &&
           $this->atomFeed->getAuthor()->getName()) {
            return $this->atomFeed->getAuthor()->getName()->getName();
        }
        return self::NOT_DEFINED_URI;
    }
    
    private function getActivityStreamsSubjectTitle() {
        if($this->atomFeed->getActivityStreamSubject() && 
           $this->atomFeed->getActivityStreamSubject()->getTitle()) {
            return $this->atomFeed->getActivityStreamSubject()->getTitle()->getTitle();
        }
        return self::NOT_DEFINED_URI;
    }
    
    public function getName() {
        $title = $this->getActivityStreamsSubjectTitle();
        if(!empty($title)) {
            return $title;
        }
        return $this->getFeedsAuthorName();
    }
    
    private function getActivityStreamsSubjectLink() {
        //then feed author
        //emtpy
    }
    
    public function getFeedsAuthorUri() {
        if($this->atomFeed->getAuthor() &&
           $this->atomFeed->getAuthor()->getUri()) {
            return $this->atomFeed->getAuthor()->getUri()->getUri();
        }
        return self::NOT_DEFINED_URI;
    }
    
    private function discoverHostMetaAndLrdd() {
        
        if(!$this->discoverHostMeta()) {
            return false;
        }
        
        if($this->hostmeta->getHostValue() != $this->domain) {
            // add log here when we have a logger for this class.
            return false;
        }
        
        if(!$this->discoverLrdd()) {
            return false;    
        }
        
        $this->atomFeedUri = $this->lrdd->getLinkAtom()->getHref();
        
        return true;
    }
    
    private function discoverHostMeta() {
        try {
            $rawHostMetaDataXML = $this->fetchURL($this->generateWellKnowHostMetaURL());
            
            $this->__log("Received following HostMeta XML from '" . $this->generateWellKnowHostMetaURL() . "': '" . $rawHostMetaDataXML . "'.");
            
            $this->setHostMeta(Xrd_HostMeta::buildFromRawXML($rawHostMetaDataXML));
        } catch (Exception $e) {
            $this->__log("Cannot read HostMeta succesfully from '" . $this->generateWellKnowHostMetaURL() . "'!");
            return false;
        }
        return true;
    }
    
    private function discoverLrdd() {
        try {
            $rawLrddXML = $this->fetchURL($this->hostmeta->generateUserXrdURL($this->uri)); 
            
            $this->__log("Received following LRDD XML from '" . $this->hostmeta->generateUserXrdURL($this->uri) . "': '" . $rawLrddXML . "'.");
            
            $this->setLrdd(Xrd_Lrdd::buildFromRawXML($rawLrddXML));
        } catch (Exception $e) {
            $this->__log("Cannot read Lrdd succesfully from '" . $this->hostmeta->generateUserXrdURL($this->uri) . "'!");
            return false;
        }
        return true;
    }
    
    private function discoverAtomFeed() {
        
        try {
            $rawAtomXML = $this->fetchURL($this->atomFeedUri); 
            
            $this->__log("Fetched following Atom feed from '" . $this->atomFeedUri . "': '" . $rawAtomXML . "'.");
            
            $this->setAtomFeed(Atom_Feed::buildFromRawXML($rawAtomXML));
        } catch (Exception $e) {
            $this->__log("Cannot read Atom Feed succesfully from '" . $this->atomFeedUri . "', ERROR: '" . $e->getMessage() . "'!");
            return false;
        }
        return true;
    }
    
    private function discoverWebfingerProfilePage() {
        try {
            $rawProfilePage = $this->fetchURL($this->uri);
            $doc = new DOMDocument;
            @$doc->loadHTML($rawProfilePage);
            $xpath = new DOMXPath($doc);
            $xpath_str = "//html/head/link[@type='application/atom+xml']";
            $entries = $xpath->evaluate($xpath_str);
            if(!$entries || !$entries->item(0)) {
                return false;
            }
            $this->atomFeedUri = $entries->item(0)->getAttribute("href");
            $this->webfingerProfilePage = $this->uri;
        } catch (Exception $e) {
            // add log here when we have a logger for this class.
            return false;
        }
        return true;
    }
    
    private function setUri($uri) {
        if(empty($uri)) {
            throw new Exception(__CLASS__ . " - URI cannot be set empty!");
        }
        $this->uri = $uri;
    }
    
    private function setDomain($domain) {
        if(empty($domain)) {
            throw new Exception(__CLASS__ . " - Domain cannot be set empty!");
        }
        $this->domain = $domain;
    }
    
    public function getDomain() {
        return $this->domain;
    }
    
    private function setHostMetaProtocol($protocol) {
        if(empty($protocol)) {
            throw new Exception(__CLASS__ . " - Protocol cannot be set empty!");
        }
        $this->hostMetaProtocol = $protocol;
    }
    
    private function setHostMeta(Xrd_Hostmeta $hostmeta) {
        if(empty($hostmeta)) {
            throw new Exception(__CLASS__ . " - Hostmeta cannot be set empty!");
        }
        $this->hostmeta = $hostmeta;
    }
    
    private function setLrdd(Xrd_Lrdd  $lrdd) {
        if(empty($lrdd)) {
            throw new Exception(__CLASS__ . " - LRDD cannot be set empty!");
        }
        $this->lrdd = $lrdd;
    }
    
    private function setAtomFeed(Atom_Feed  $atom) {
        if(empty($atom)) {
            throw new Exception(__CLASS__ . " - Atom Feed cannot be set empty!");
        }
        $this->atomFeed = $atom;
    }
    
    //TODO add httpFetch handler so that the 'engine' to do the fetch
    // can be overriden.
    protected function fetchURL($url) {
        return file_get_contents($url);
    }
    
    protected function generateWellKnowHostMetaURL() {
        return $this->hostMetaProtocol . '://' . $this->domain . '/.well-known/host-meta';
    }
    
    private function __log($str) {
        if($this->__debugoutput) {
            echo $str . "\n";
        }
    }
}